use serde::{Deserialize, Serialize};
use shellexpand::tilde;
use std::path::PathBuf;

use crate::{
    argument::Argument,
    c::CONFIG_FILE_NAME,
    config::{Config, error::Error, raw::raw_proxy_entry::RawProxyEntry},
    util::Util,
};

use super::super::Result;

#[derive(Debug, Deserialize, Serialize)]
pub struct RawConfig {
    pub port: u16,
    pub enable_tls: bool,
    pub cert_path: Option<String>,
    pub key_path: Option<String>,
    pub proxies: Vec<RawProxyEntry>,
}

impl RawConfig {
    pub fn with_argument(a: &Argument) -> Self {
        let p = a
            .config_path()
            .clone()
            .unwrap_or_else(|| Util::app_dirs().config_dir().join(CONFIG_FILE_NAME));

        config::Config::builder()
            .set_default("port", 80)
            .unwrap()
            .add_source(config::File::with_name(p.to_str().unwrap()).required(false))
            .set_override_option("port", *a.port())
            .unwrap()
            .build()
            .unwrap()
            .try_deserialize()
            .unwrap()
    }
}

impl TryFrom<RawConfig> for Config {
    type Error = Error;

    fn try_from(value: RawConfig) -> std::result::Result<Self, Self::Error> {
        let port = value.port;
        let enable_tls = value.enable_tls;
        let cert_path = value
            .cert_path
            .map(|p| tilde(&p).to_string())
            .map(PathBuf::from);
        let key_path = value
            .key_path
            .map(|p| tilde(&p).to_string())
            .map(PathBuf::from);
        let proxy = value
            .proxies
            .into_iter()
            .map(|e| e.take_router_and_setting())
            .collect::<Result<_>>()?;

        Ok(Self {
            port,
            enable_tls,
            cert_path,
            key_path,
            proxies: proxy,
        })
    }
}

impl From<Config> for RawConfig {
    fn from(value: Config) -> Self {
        let port = value.port;
        let enable_tls = value.enable_tls;
        let cert_path = value.cert_path.map(|path| path.to_string_lossy().to_string());
        let key_path = value.key_path.map(|path| path.to_string_lossy().to_string());

        let proxies = value
            .proxies
            .into_iter()
            .map(|(k, setting)| {
                let rewrite = setting.rewrite();
                let upstreams = setting
                    .take_upstreams()
                    .into_iter()
                    .map(Into::into)
                    .collect();

                RawProxyEntry {
                    router: k.into(),
                    rewrite,
                    upstreams,
                }
            })
            .collect();

        Self {
            port,
            enable_tls,
            cert_path,
            key_path,
            proxies,
        }
    }
}

#[cfg(test)]
mod test {
    use crate::config::raw::raw_config::RawConfig;
    use crate::config::raw::raw_proxy_entry::RawProxyEntry;
    use std::path::PathBuf;

    #[test]
    fn gen_config() {
        let config = RawConfig {
            port: 11850,
            enable_tls: true,
            key_path: Some(String::from("abc.com.key")),
            cert_path: Some(String::from("abc.com.cert")),
            proxies: vec![
                RawProxyEntry {
                    router: "/abc".to_string(),
                    rewrite: true,
                    upstreams: vec!["127.0.0.1:10001".to_string(), "127.0.0.1:10002".to_string()],
                },
                RawProxyEntry {
                    router: "/def".to_string(),
                    rewrite: true,
                    upstreams: vec!["127.0.0.1:10003".to_string(), "127.0.0.1:10004".to_string()],
                },
            ],
        };

        let s = toml::to_string(&config).unwrap();
        println!("{}", s);
    }
}
